package za.co.felixsol.sf.dxf.reader.io;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import za.co.felixsol.sf.dxf.reader.DxfBlocks;
import za.co.felixsol.sf.dxf.reader.DxfClasses;
import za.co.felixsol.sf.dxf.reader.DxfEntities;
import za.co.felixsol.sf.dxf.reader.DxfEntry;
import za.co.felixsol.sf.dxf.reader.DxfHeader;
import za.co.felixsol.sf.dxf.reader.DxfObjects;
import za.co.felixsol.sf.dxf.reader.DxfProgram;
import za.co.felixsol.sf.dxf.reader.DxfSection;
import za.co.felixsol.sf.dxf.reader.DxfTables;
import za.co.felixsol.sf.dxf.reader.DxfThumbnailImage;
import za.co.felixsol.sf.dxf.reader.entity.DxfEntityFactory;

public abstract class DxfReader {

	private Stack<DxfEntry> dxfEntryStack;

	public DxfReader() {
		dxfEntryStack = new Stack<DxfEntry>();
	}

	protected abstract void initReader() throws IOException;

	protected abstract void doneReader() throws IOException;

	protected abstract DxfEntry getNextDxfEntry() throws IOException;

	protected DxfEntry getNextDxfEntryFromStreamOrStack() throws IOException {
		if (!dxfEntryStack.empty()) {
			return dxfEntryStack.pop();
		} else {
			return getNextDxfEntry();
		}
	}

	public DxfProgram readProgram() throws IOException {
		initReader();
		try {
			DxfProgram result = new DxfProgram();
			List<DxfSection> sections = result.getSections();
			DxfEntry DxfEntry = getNextDxfEntryFromStreamOrStack();
			while (!DxfEntry.getValue().equals("EOF")) {

				// Try get section.....
				if (DxfEntry.getValue().equals("SECTION")) {
					parseSection(result);
				}

				DxfEntry = getNextDxfEntryFromStreamOrStack();
			}
			return result;
		} finally {
			doneReader();
		}
	}

	private void parseSection(DxfProgram dxfProgram) throws IOException {
		DxfEntry dxfEntry = getNextDxfEntryFromStreamOrStack();
		while (!dxfEntry.getValue().equals("ENDSEC")) {
			if (dxfEntry.getValue().equals("HEADER")) {
				parseHeader(dxfProgram);
			} else if (dxfEntry.getValue().equals("CLASSES")) {
				parseClasses(dxfProgram);
			} else if (dxfEntry.getValue().equals("TABLES")) {
				parseTables(dxfProgram);
			} else if (dxfEntry.getValue().equals("BLOCKS")) {
				parseBlocks(dxfProgram);
			} else if (dxfEntry.getValue().equals("ENTITIES")) {
				parseEntities(dxfProgram);
			} else if (dxfEntry.getValue().equals("OBJECTS")) {
				parseObjects(dxfProgram);
			} else if (dxfEntry.getValue().equals("THUMBNAILIMAGE")) {
				parseThumbnailImages(dxfProgram);
			} else {
				throw new RuntimeException("Unknown section encountered - "
						+ dxfEntry.getValue());
			}
			dxfEntry = getNextDxfEntryFromStreamOrStack();
		}

	}

	private void parseHeader(DxfProgram dxfProgram) throws IOException {
		DxfHeader dxfSection= new DxfHeader(dxfProgram);
		dxfProgram.getSections().add(dxfSection);
		DxfEntry dxfEntry = getNextDxfEntryFromStreamOrStack();
		while (!dxfEntry.getValue().equals("ENDSEC")) {
			dxfEntry = getNextDxfEntryFromStreamOrStack();
		}
		dxfEntryStack.push(dxfEntry);
	}

	private void parseClasses(DxfProgram dxfProgram) throws IOException {
		DxfClasses dxfSection= new DxfClasses(dxfProgram);
		dxfProgram.getSections().add(dxfSection);
		DxfEntry dxfEntry = getNextDxfEntryFromStreamOrStack();
		while (!dxfEntry.getValue().equals("ENDSEC")) {
			dxfEntry = getNextDxfEntryFromStreamOrStack();
		}
		dxfEntryStack.push(dxfEntry);
	}

	private void parseTables(DxfProgram dxfProgram) throws IOException {
		DxfTables dxfSection= new DxfTables(dxfProgram);
		dxfProgram.getSections().add(dxfSection);
		DxfEntry dxfEntry = getNextDxfEntryFromStreamOrStack();
		while (!dxfEntry.getValue().equals("ENDSEC")) {
			dxfEntry = getNextDxfEntryFromStreamOrStack();
		}
		dxfEntryStack.push(dxfEntry);
	}

	private void parseBlocks(DxfProgram dxfProgram) throws IOException {
		DxfBlocks dxfSection= new DxfBlocks(dxfProgram);
		dxfProgram.getSections().add(dxfSection);
		DxfEntry dxfEntry = getNextDxfEntryFromStreamOrStack();
		while (!dxfEntry.getValue().equals("ENDSEC")) {
			dxfEntry = getNextDxfEntryFromStreamOrStack();
		}
		dxfEntryStack.push(dxfEntry);
	}

	private void parseEntities(DxfProgram dxfProgram) throws IOException {
		DxfEntities dxfSection= new DxfEntities(dxfProgram);
		dxfProgram.getSections().add(dxfSection);
		DxfEntry dxfEntry = getNextDxfEntryFromStreamOrStack();
		while (!dxfEntry.getValue().equals("ENDSEC")) {
			dxfEntryStack.push(dxfEntry);
			parseEntity(dxfProgram, dxfSection);
			dxfEntry = getNextDxfEntryFromStreamOrStack();
		}
		dxfEntryStack.push(dxfEntry);
	}
	
	private void parseEntity(DxfProgram dxfProgram, DxfEntities dxfEntities) throws IOException {
		List<DxfEntry> entries = new ArrayList<DxfEntry>();
		DxfEntry dxfEntityType = getNextDxfEntryFromStreamOrStack();
		DxfEntry dxfEntry = getNextDxfEntryFromStreamOrStack();
		while (!dxfEntry.getCode().equals(0)) {
			entries.add(dxfEntry);
			dxfEntry = getNextDxfEntryFromStreamOrStack();
		}
		dxfEntities.getEntities().add(DxfEntityFactory.createEntity(dxfEntities, dxfEntityType, entries));
		dxfEntryStack.push(dxfEntry);
	}

	private void parseObjects(DxfProgram dxfProgram) throws IOException {
		DxfObjects dxfSection= new DxfObjects(dxfProgram);
		dxfProgram.getSections().add(dxfSection);
		DxfEntry dxfEntry = getNextDxfEntryFromStreamOrStack();
		while (!dxfEntry.getValue().equals("ENDSEC")) {
			dxfEntry = getNextDxfEntryFromStreamOrStack();
		}
		dxfEntryStack.push(dxfEntry);
	}

	private void parseThumbnailImages(DxfProgram dxfProgram) throws IOException {
		DxfThumbnailImage dxfSection= new DxfThumbnailImage(dxfProgram);
		dxfProgram.getSections().add(dxfSection);
		DxfEntry dxfEntry = getNextDxfEntryFromStreamOrStack();
		while (!dxfEntry.getValue().equals("ENDSEC")) {
			dxfEntry = getNextDxfEntryFromStreamOrStack();
		}
		dxfEntryStack.push(dxfEntry);
	}

}
